<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div style="height: 100px">
    <button style="position: absolute; top: 10px; left: 0px;">点我1</button>
    <button style="position: absolute; top: 50px;left: 0px;">点我2</button>
  </div>
  <div>防抖：<input type="text" id="input1"></div>
  <div>截流：<input type="text" id="input2"></div>
</body>

<script type="text/javascript">
  console.log('------------ 作用域 ------------');
  // var let const的区别
  // let const能创造块级作用域 var不能
  // let不能变量提升 var可以
  // const一旦定义就必须马上赋值 并且后续不可更改
  (() => {
    var a = 1
    function f() {
      // 1 在下面定义了var a = 2, var有变量提升
      // 相当于在函数开始定义了var a; 然后在原var a = 2的地方a = 2, 所以这里是undefined
      console.log(1, a)
      // 2 b没有修饰符 无论写到哪里都是全局的 全局变量及let及const没有变量提升
      // 所以这里 error b is not defined
      // console.log(2, b)
      var a = 2
      b = 3
      let c = 4
      // 3 沿作用域链往上找 如果内部没找到 还可以往函数外部找 因为函数内部可以访问外部
      // 内部就找到了a=2
      console.log(3, a)
    }
    // 4 函数还没执行 全局变量b还没定义 error
    // console.log(4, b)
    f()
    // 5 函数已经执行 找到全局变量b
    console.log(5, b)
    // 6 函数内部可以访问外部, 函数外部不能访问内部
    // 所以只能访问到全局作用域var a = 1
    console.log(6, a)
    // 7 函数内部可以访问外部, 函数外部不能访问内部
    // 访问不了内部c error
    // console.log(7, c)
  })()


  console.log('------------ 闭包 ------------');
  // 1 函数内部可以访问函数外部变量，函数外部不能访问函数内部变量
  (() => {
    let a = 'a'
    function fn() {
      let b = 'b'
      // 内可以访问外
      console.log(1, a)
      // 外不能访问内
      // console.log(1, c)
      function fnn() {
        let c = 'c'
        // 内可以访问外
        console.log(1, b)
      }
      fnn()
    }
    fn()
    // 外不能访问内
    // console.log(1, b)
  })();

  // 2 函数里面return函数 闭包
  // 通过闭包(函数里return的这个函数)这个桥梁
  // 1 可以从函数外部操作函数内部的变量a2
  // 2 使a2常驻内存
  (() => {
    function fn() {
      let a2 = 1;
      return () => {
        console.log('操作局部变量a2', ++a2)
      }
    }
    let fnn = fn()
    fnn() // 2
    fnn() // 3
    // 闭包内存释放
    // fnn = null
  })();

  // 3 模拟私有变量 函数外部通过闭包这个桥梁get set某个私有变量
  (() => {
    function C() {
      let a = 111
      return {
        getA: () => {
          return a;
        },
        setA: (value) => {
          a = value
        }
      }
    }
    const c = C()
    console.log(3, c.getA())
    c.setA(555)
    console.log(3, c.getA())
  })();

  // 4 闭包应用 回调函数
  (() => {
    const arr = [1, 2, 3, 4, 5, 6]
    const between = (a, b) => {
      return (v) => {
        return v >= a && v <= b
      }
    }
    const resArr = arr.filter(between(2, 4))
    console.log(4, resArr)
  })();

  // 5 闭包应用 防抖
  (() => {
    function debounce(delay) {
      let timer = null;
      return (callBack) => {
        // 闭包 timer常驻内存 所以这里可以清空上一次执行产生的timer
        clearTimeout(timer)
        timer = null
        // delay毫秒后才开始执行callBack 
        // 如果delay毫秒内又调用了这里 重新开始延时delay毫秒
        timer = setTimeout(callBack, delay)
      }
    }
    const debounceFunc = debounce(1000)
    const input1 = document.querySelector('#input1')
    input1.addEventListener('keyup', (e) => {
      debounceFunc(() => {
        console.log(e.target.value)
      })
    })
  })();

  // 6 闭包应用 截流
  (() => {
    function throttle(delay) {
      let timer = null
      return (callBack) => {
        // 闭包 timer常驻内存 所以这里可以判断上一次生成的timer是否为空
        if (!timer) {
          // 一个delay时间段内只做一次callBack 做完了才能重新开定时器
          timer = setTimeout(() => {
            callBack()
            clearTimeout(timer)
            timer = null
          }, delay)
        }
      }
    }
    const throttleFunc = throttle(1000)
    const input2 = document.querySelector('#input2')
    input2.addEventListener('keyup', (e) => {
      throttleFunc(() => {
        console.log(e.target.value)
      })
    })
  })();


  console.log('------------ 创建块级作用域 ------------');
  // 1 如何创建块级作用域
  // const let用花括号里
  // var用匿名函数自我执行
  (() => {
    {
      var a31 = '31'
    }
    {
      let a32 = '32'
    }
    {
      const a33 = '33'
    }
    // var没有块级作用域 这里a31等同于全局的a31
    console.log(1, a31)
    // 后出的let const都有块级作用域 这里访问不到
    // console.log(a32)
    // console.log(a33)
  })();

  // 2 for等语句的块级作用域
  (() => {
    function fn41() {
      for (var i = 1; i <= 3; i++) {
        // 每次循环开辟一个独立的空间存放setTimeout 一共三个空间
        // 由于var没有块级作用域 i每次循环改变的都是全局的window.i
        // for循环快速走完后 i(window.i)等于4
        // 之后1 2 3秒执行console i的时候都是 4
        setTimeout(() => {
          console.log(i)
        }, 1000 * i)
      }
    }
    function fn42() {
      // 每次循环开辟一个独立的空间存放setTimeout 一共三个空间
      // let有块级作用域 i每次循环都单独存放在三面三个空间里
      // 之后1 2 3秒执行console i的时候是 1 2 3
      for (let i = 1; i <= 3; i++) {
        setTimeout(() => {
          console.log(i)
        }, 1000 * i)
      }
    }
    function fn43() {
      for (var i = 1; i <= 3; i++) {
        // var没有块级作用域 通过匿名函数自我执行创建块级作用域
        // 之后1 2 3秒执行console i的时候是 1 2 3
        ((n) => {
          setTimeout(() => {
            console.log(n)
          }, 1000 * n)
        })(i)
      }
    }
    function fn44() {
      let arr = []
      for (var i = 1; i <= 3; i++) {
        // var没有块级作用域 通过匿名函数自我执行创建块级作用域
        ((n) => {
          arr.push(() => {
            return n
          })
        })(i)
      }
      console.log(2, arr[0]())
      console.log(2, arr[1]())
      console.log(2, arr[2]())
    }
    // fn43()
  })();

  
  console.log('------------ this ------------');
  // 1 全局函数和对象下的this指向
  {
    // 这里是{}创建的块级作用域 let const不再是window全局变量
    // 而var在{}里没有块级作用域(只能在匿名函数自我执行力) 所以这里等于window.name
    var name = 222
    const a = {
      name: 111,
      say: function(){
        console.log(this.name, this)
      }
    }
    const fun = a.say
    // 这里相当于window.fun()相当于fun.call(window) 所以是window.name 222
    fun()
    // 这里相当于a.say.call(a) 所以是a.name 111
    a.say()

    const b = {
      name: 333,
      say: function(fun) {
        fun()
      }
    }
    // 最后指向的是fun() 相当于window.fun()相当于fun.call(window) 所以是window.name 222
    b.say(a.say)
    b.say = a.say
    // 这里相应的b.say.call(b) 所以是b.name 333
    b.say()
  }

  // 2 箭头函数的this指向
  {
    var age = 222
    const obj = {
      age: 111,
      showAge: function() {
        return () => {
          // this往上找 遇到showAge: function 所以这里this指向obj 输出111
          console.log(this.age, this)
        }
      },
      aa: {
        bb: () => {
          // this一直往上找 没有遇到function 所以this是window 输出222
          console.log(this.age, this)
        }
      }
    }
    obj.showAge()()
    obj.aa.bb()
  }

  console.log('------------ 浅拷贝深拷贝 ------------');
  {
    // 1 赋值 赋值了引用地址 指向同一个堆 相互影响
    const person1 = { name: 'a' }
    const person2 = person1
    person2.name = 'aa'
    // 都输出aa
    console.log(1, person1.name, person2.name)

    // 2 浅拷贝 只拷贝了一层 并且某个value是引用类型 那只拷贝了引用 还是会相互影响
    function shallowClone(source) {
      const newObj = {}
      for(let i in source) {
        if(source.hasOwnProperty(i)) {
          newObj[i] = source[i]
        }
      }
      return newObj
    }
    const person3 = { name: 'c', hobby: [1, 2, 3] }
    const person4 = shallowClone(person3)
    // person3.name person4.name 因为是基本数据类型 拷贝后不再相互影响 
    person4.name = 'cc'
    // person3.hobby person4.hobby 因为是引用类型 拷贝了引用后指向了同一个堆 相互影响
    person4.hobby[2] = 0
    console.log(2, person3, person4)

    // 3 深拷贝(递归) 可拷贝多层 只能是数组/对象不能是函数等 
    // 如果是引用类型继续递归拷贝 直到拷贝基本数据类型 不会相互影响
    function deepClone(source) {
      let newObj = null
      if(source.constructor === Array) {
        newObj = []
      } else if(source.constructor === Object) {
        newObj = {}
      } else {
        return source
      }
      for(let i in source) {
        if(source.hasOwnProperty(i)) {
          if(source[i].constructor === Array || source[i].constructor === Object) {
            newObj[i] = deepClone(source[i])
          } else {
            newObj[i] = source[i]
          }
        }
      }
      return newObj
    }
    const person5 = { name: 'c', hobby: [1, 2, 3]}
    const person6 = deepClone(person5)
    // person5.name person6.name 不再相互影响 基本数据类型
    person6.name = 'cc'
    // person5.hobby person6.hobby 不再相互影响 递归拷贝了值
    person6.hobby[2] = 0
    console.log(3, person5, person6)

    // 4 JSON方法 可拷贝多层 只能是数组对象不能是函数等 
    // const newItem = JSON.parse(JSON.stringify(item))

    // 5 es6对象展开 只能拷贝一层
    // obj2 = { ...obj1 }

    // 6 Object.assign() 只能拷贝一层
    // const obj2 = Object.assign({}, { name: 1 })
  }




</script>

</html>